/*
 * SoapUI, Copyright (C) 2004-2017 SmartBear Software
 *
 * Licensed under the EUPL, Version 1.1 or - as soon as they will be approved by the European Commission - subsequent 
 * versions of the EUPL (the "Licence"); 
 * You may not use this work except in compliance with the Licence. 
 * You may obtain a copy of the Licence at: 
 * 
 * http://ec.europa.eu/idabc/eupl 
 * 
 * Unless required by applicable law or agreed to in writing, software distributed under the Licence is 
 * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 
 * express or implied. See the Licence for the specific language governing permissions and limitations 
 * under the Licence. 
 */

package com.eviware.soapui;

import com.eviware.soapui.impl.WorkspaceImpl;
import com.eviware.soapui.impl.actions.ImportWsdlProjectAction;
import com.eviware.soapui.impl.actions.SwitchWorkspaceAction;
import com.eviware.soapui.impl.wsdl.WsdlProject;
import com.eviware.soapui.model.ModelItem;
import com.eviware.soapui.model.iface.Interface;
import com.eviware.soapui.model.iface.Operation;
import com.eviware.soapui.model.iface.Request;
import com.eviware.soapui.model.mock.MockOperation;
import com.eviware.soapui.model.mock.MockResponse;
import com.eviware.soapui.model.mock.MockService;
import com.eviware.soapui.model.project.Project;
import com.eviware.soapui.model.support.WorkspaceListenerAdapter;
import com.eviware.soapui.model.testsuite.LoadTest;
import com.eviware.soapui.model.testsuite.TestCase;
import com.eviware.soapui.model.testsuite.TestStep;
import com.eviware.soapui.model.testsuite.TestSuite;
import com.eviware.soapui.model.workspace.Workspace;
import com.eviware.soapui.model.workspace.WorkspaceListener;
import com.eviware.soapui.support.UISupport;
import com.eviware.soapui.support.action.SoapUIActionMapping;
import com.eviware.soapui.support.action.support.DefaultActionMapping;
import com.eviware.soapui.support.action.swing.SwingActionDelegate;
import com.eviware.soapui.support.types.StringToStringMap;
import com.eviware.soapui.ui.desktop.DesktopListener;
import com.eviware.soapui.ui.desktop.DesktopPanel;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;
import java.awt.event.ActionEvent;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.util.Map;

/**
 * Workspace/Deskopt Listener that updates the recent menus..
 *
 * @author ole.matzura
 */

public class RecentItemsListener extends WorkspaceListenerAdapter implements WorkspaceListener, DesktopListener {
    private static final String RECENT_WORKSPACES_SETTING = "RecentWorkspaces";
    private static final String RECENT_PROJECTS_SETTING = "RecentProjects";
    private static final String EMPTYMARKER = "- empty -";
    private static final String CLEAR_ITEMS = "Clear Items";
    private JMenu recentProjectsMenu;
    private JMenu recentWorkspacesMenu;
    private JMenu recentEditorsMenu;
    private boolean switchingWorkspace;

    public RecentItemsListener(JMenu recentWorkspacesMenu2, JMenu recentProjectsMenu2, JMenu recentEditorsMenu2) {
        recentWorkspacesMenu = recentWorkspacesMenu2;
        recentProjectsMenu = recentProjectsMenu2;
        recentEditorsMenu = recentEditorsMenu2;
        recentEditorsMenu.add(EMPTYMARKER).setEnabled(false);
        recentEditorsMenu.getPopupMenu().addPopupMenuListener(new PopupMenuListener() {

            public void popupMenuCanceled(PopupMenuEvent e) {
            }

            public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
            }

            public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
                int editorCount = 0;
                for (int c = 0; c < recentEditorsMenu.getItemCount(); c++) {
                    ShowEditorAction action = getShowEditorAction(recentEditorsMenu, c);
                    if (action == null) {
                        continue;
                    }

                    editorCount++;

                    if (action.isReleased()) {
                        recentEditorsMenu.remove(c);
                        c--;
                    } else {
                        try {
                            action.update();
                        } catch (Throwable e1) {
                            recentEditorsMenu.remove(c);
                            c--;
                        }
                    }
                }

                // Look for a Clear Items JMenuItem, and create it if not existing
                JMenuItem clearAllItem = null;
                for (int i = 0; i < recentEditorsMenu.getItemCount(); ++i) {
                    JMenuItem editorItem = recentEditorsMenu.getItem(i);
                    if (editorItem == null) {
                        continue;
                    }

                    if (editorItem.getText().equals(CLEAR_ITEMS)) {
                        clearAllItem = editorItem;
                    }
                }
                if (clearAllItem == null) {
                    clearAllItem = new JMenuItem(new ClearEditorsAction());
                    recentEditorsMenu.addSeparator();
                    recentEditorsMenu.add(clearAllItem);
                }

                clearAllItem.setEnabled(editorCount > 0);

                // Create Empty Marker if needed.
                if (editorCount == 0 && recentEditorsMenu.getItemCount() <= 2) {
                    recentEditorsMenu.add(new JMenuItem(EMPTYMARKER), 0).setEnabled(false);
                }

            }
        });

        updateRecentWorkspacesMenu();
        updateRecentProjectsMenu();
    }

    @SuppressWarnings("unchecked")
    private void updateRecentWorkspacesMenu() {
        String recent = SoapUI.getSettings().getString(RECENT_WORKSPACES_SETTING, null);
        StringToStringMap history = recent == null ? new StringToStringMap() : StringToStringMap.fromXml(recent);

        recentWorkspacesMenu.removeAll();

        if (history.size() > 0) {
            for (Map.Entry<String, String> entry : history.entrySet()) {
                String filePath = entry.getKey();
                DefaultActionMapping<WorkspaceImpl> mapping = new DefaultActionMapping<WorkspaceImpl>(
                        SwitchWorkspaceAction.SOAPUI_ACTION_ID, null, null, false, filePath);
                String wsName = entry.getValue();

                if (SoapUI.getWorkspace().getPath().equals(filePath)) {
                    continue;
                }

                mapping.setName(wsName);
                mapping.setDescription("Switches to the [" + wsName + "] workspace");

                AbstractAction delegate = new SwingActionDelegate(mapping, SoapUI.getWorkspace());
                recentWorkspacesMenu.add(new JMenuItem(delegate));
            }
        } else {
            recentWorkspacesMenu.add(EMPTYMARKER).setEnabled(false);
        }

        recentWorkspacesMenu.addSeparator();
        recentWorkspacesMenu.add(new ClearWorkspacesAction()).setEnabled(history.size() > 0);
    }

    @SuppressWarnings("unchecked")
    private void updateRecentProjectsMenu() {
        recentProjectsMenu.removeAll();

        String recent = SoapUI.getSettings().getString(RECENT_PROJECTS_SETTING, null);
        StringToStringMap history = recent == null ? new StringToStringMap() : StringToStringMap.fromXml(recent);

        if (history.size() > 0) {
            for (Map.Entry<String, String> entry : history.entrySet()) {
                String filePath = entry.getKey();
                DefaultActionMapping<WorkspaceImpl> mapping = new DefaultActionMapping<WorkspaceImpl>(
                        ImportWsdlProjectAction.SOAPUI_ACTION_ID, null, null, false, filePath);
                String wsName = entry.getValue();
                mapping.setName(wsName);
                mapping.setDescription("Switches to the [" + wsName + "] project");

                AbstractAction delegate = new SwingActionDelegate(mapping, SoapUI.getWorkspace());
                recentProjectsMenu.add(new JMenuItem(delegate));
            }
        } else {
            recentProjectsMenu.add(EMPTYMARKER).setEnabled(false);
        }

        recentProjectsMenu.addSeparator();
        recentProjectsMenu.add(new ClearProjectsAction()).setEnabled(history.size() > 0);
    }

    @SuppressWarnings("unchecked")
    public void projectAdded(Project project) {
        if (switchingWorkspace) {
            return;
        }

        String filePath = ((WsdlProject) project).getPath();
        if (filePath == null) {
            return;
        }

        String recent = SoapUI.getSettings().getString(RECENT_PROJECTS_SETTING, null);
        if (recent != null) {
            StringToStringMap history = StringToStringMap.fromXml(recent);
            history.remove(filePath);
            SoapUI.getSettings().setString(RECENT_PROJECTS_SETTING, history.toXml());
        }

        for (int c = 0; c < recentProjectsMenu.getItemCount() - 2; c++) {
            JMenuItem item = recentProjectsMenu.getItem(c);
            if (item == null) {
                continue;
            }

            Action action = item.getAction();
            if (!(action instanceof SwingActionDelegate)) {
                continue;
            }

            SwingActionDelegate actionDelegate = (SwingActionDelegate) action;
            if (actionDelegate == null) {
                continue;
            }

            SoapUIActionMapping mapping = actionDelegate.getMapping();
            if (filePath.equals(mapping.getParam())) {
                recentProjectsMenu.remove(c);
                break;
            }
        }

        if (recentProjectsMenu.getItemCount() == 2) {
            recentProjectsMenu.add(new JMenuItem(EMPTYMARKER), 0).setEnabled(false);
            recentProjectsMenu.getItem(recentProjectsMenu.getItemCount() - 1).setEnabled(false);
        }
    }

    public void projectChanged(Project project) {
    }

    @SuppressWarnings("unchecked")
    public void projectRemoved(Project project) {
        if (switchingWorkspace) {
            return;
        }

        String filePath = ((WsdlProject) project).getPath();

        String recent = SoapUI.getSettings().getString(RECENT_PROJECTS_SETTING, null);
        StringToStringMap history = recent == null ? new StringToStringMap() : StringToStringMap.fromXml(recent);
        history.put(filePath, project.getName());
        SoapUI.getSettings().setString(RECENT_PROJECTS_SETTING, history.toXml());

        DefaultActionMapping<WorkspaceImpl> mapping = new DefaultActionMapping<WorkspaceImpl>(
                ImportWsdlProjectAction.SOAPUI_ACTION_ID, null, null, false, filePath);
        mapping.setName(project.getName());
        mapping.setDescription("Switches to the [" + project.getName() + "] project");

        AbstractAction delegate = new SwingActionDelegate(mapping, SoapUI.getWorkspace());

        recentProjectsMenu.add(new JMenuItem(delegate), recentProjectsMenu.getItemCount() - 2);

        recentProjectsMenu.getItem(recentProjectsMenu.getItemCount() - 1).setEnabled(true);

        if (isEmptyMarker(recentProjectsMenu.getItem(0))) {
            recentProjectsMenu.remove(0);
        }

        removeProjectEditors(project);

    }

    private void removeProjectEditors(Project project) {
        for (int c = 0; c < recentEditorsMenu.getItemCount(); c++) {
            ShowEditorAction action = getShowEditorAction(recentEditorsMenu, c);
            if (action == null) {
                continue;
            }

            if (action.isReleased()) {
                recentEditorsMenu.remove(c);
                c--;
            } else {
                try {
                    action.update();
                    if (dependsOnProject(action.getModelItem(), project)) {
                        recentEditorsMenu.remove(c);
                        c--;
                    }
                } catch (Throwable e1) {
                    recentEditorsMenu.remove(c);
                    c--;
                }
            }
        }
    }

    private boolean dependsOnProject(ModelItem modelItem, Project project) {
        if (modelItem instanceof Interface) {
            return ((Interface) modelItem).getProject() == project;
        } else if (modelItem instanceof Operation) {
            return ((Operation) modelItem).getInterface().getProject() == project;
        } else if (modelItem instanceof Request) {
            return ((Request) modelItem).getOperation().getInterface().getProject() == project;
        } else if (modelItem instanceof TestSuite) {
            return ((TestSuite) modelItem).getProject() == project;
        } else if (modelItem instanceof TestCase) {
            return ((TestCase) modelItem).getTestSuite().getProject() == project;
        } else if (modelItem instanceof TestStep) {
            return ((TestStep) modelItem).getTestCase().getTestSuite().getProject() == project;
        } else if (modelItem instanceof LoadTest) {
            return ((LoadTest) modelItem).getTestCase().getTestSuite().getProject() == project;
        } else if (modelItem instanceof MockService) {
            return ((MockService) modelItem).getProject() == project;
        } else if (modelItem instanceof MockOperation) {
            return ((MockOperation) modelItem).getMockService().getProject() == project;
        } else if (modelItem instanceof MockResponse) {
            return ((MockResponse) modelItem).getMockOperation().getMockService().getProject() == project;
        }

        return false;
    }

    @SuppressWarnings("unchecked")
    public void workspaceSwitched(Workspace workspace) {
        switchingWorkspace = false;

        String filePath = workspace.getPath();

        String recent = SoapUI.getSettings().getString(RECENT_WORKSPACES_SETTING, null);
        if (recent != null) {
            StringToStringMap history = StringToStringMap.fromXml(recent);
            history.remove(filePath);
            SoapUI.getSettings().setString(RECENT_WORKSPACES_SETTING, history.toXml());
        }

        for (int c = 0; c < recentWorkspacesMenu.getItemCount(); c++) {
            JMenuItem item = recentWorkspacesMenu.getItem(c);
            if (item == null) {
                continue;
            }

            Action action = item.getAction();
            if (!(action instanceof SwingActionDelegate)) {
                continue;
            }

            SwingActionDelegate actionDelegate = (SwingActionDelegate) action;
            if (actionDelegate == null) {
                continue;
            }

            SoapUIActionMapping mapping = actionDelegate.getMapping();
            if (filePath.equals(mapping.getParam())) {
                recentWorkspacesMenu.remove(c);
                break;
            }
        }

        if (recentWorkspacesMenu.getItemCount() == 2) {
            recentWorkspacesMenu.add(new JMenuItem(EMPTYMARKER), 0).setEnabled(false);
        }
    }

    @SuppressWarnings("unchecked")
    public void workspaceSwitching(Workspace workspace) {
        switchingWorkspace = true;
        recentEditorsMenu.removeAll();
        if (recentEditorsMenu.getItemCount() == 0) {
            recentEditorsMenu.add(EMPTYMARKER).setEnabled(false);
        }

        String filePath = workspace.getPath();
        DefaultActionMapping<WorkspaceImpl> mapping = new DefaultActionMapping<WorkspaceImpl>(
                SwitchWorkspaceAction.SOAPUI_ACTION_ID, null, null, false, filePath);
        mapping.setName(workspace.getName());
        mapping.setDescription("Switches to the [" + workspace.getName() + "] workspace");

        AbstractAction delegate = new SwingActionDelegate(mapping, SoapUI.getWorkspace());
        recentWorkspacesMenu.add(new JMenuItem(delegate), recentWorkspacesMenu.getItemCount() - 2);

        recentWorkspacesMenu.getItem(recentWorkspacesMenu.getItemCount() - 1).setEnabled(true);
        System.out.println(recentWorkspacesMenu.getItem(recentWorkspacesMenu.getItemCount() - 1).getText());

        String recent = SoapUI.getSettings().getString(RECENT_WORKSPACES_SETTING, null);
        StringToStringMap history = recent == null ? new StringToStringMap() : StringToStringMap.fromXml(recent);
        history.put(filePath, workspace.getName());
        SoapUI.getSettings().setString(RECENT_WORKSPACES_SETTING, history.toXml());

        if (isEmptyMarker(recentWorkspacesMenu.getItem(0))) {
            recentWorkspacesMenu.remove(0);
        }

        recentEditorsMenu.removeAll();
    }

    public void desktopPanelClosed(DesktopPanel desktopPanel) {
        ModelItem modelItem = desktopPanel.getModelItem();
        if (modelItem == null || recentEditorsMenu.getItemCount() == 0) {
            return;
        }

        if (isEmptyMarker(recentEditorsMenu.getItem(0))) {
            recentEditorsMenu.remove(0);
        }

        recentEditorsMenu.add(new JMenuItem(new ShowEditorAction(modelItem)), 0);
    }

    public void desktopPanelCreated(DesktopPanel desktopPanel) {
        for (int c = 0; c < recentEditorsMenu.getItemCount(); c++) {
            ShowEditorAction action = getShowEditorAction(recentEditorsMenu, c);
            if (action == null) {
                continue;
            }

            if (action.isReleased()) {
                recentEditorsMenu.remove(c);
                c--;
            } else if (action.getModelItem().equals(desktopPanel.getModelItem())) {
                recentEditorsMenu.remove(c);
                break;
            }
        }

        if (recentEditorsMenu.getItemCount() == 2) {
            recentEditorsMenu.add(new JMenuItem(EMPTYMARKER), 0).setEnabled(false);
        }
    }

    public void desktopPanelSelected(DesktopPanel desktopPanel) {
    }

    private static class ShowEditorAction extends AbstractAction {
        private Reference<ModelItem> ref;

        public ShowEditorAction(ModelItem modelItem) {
            super(modelItem.getName());

            putValue(Action.SHORT_DESCRIPTION, "Reopen editor for [" + modelItem.getName() + "]");
            ref = new WeakReference<ModelItem>(modelItem);
        }

        public ModelItem getModelItem() {
            return ref.get();
        }

        public void update() {
            ModelItem modelItem = ref.get();
            if (modelItem == null) {
                return;
            }

            putValue(Action.NAME, modelItem.getName());
            putValue(Action.SHORT_DESCRIPTION, "Reopen editor for [" + modelItem.getName() + "]");
        }

        public boolean isReleased() {
            return ref.get() == null;
        }

        public void actionPerformed(ActionEvent e) {
            ModelItem modelItem = ref.get();
            if (modelItem != null) {
                UISupport.showDesktopPanel(modelItem);
            } else {
                UISupport.showErrorMessage("Item [" + getValue(Action.NAME) + "] is no longer available");
            }
        }
    }

	/* Helper methods */

    /**
     * Confirms that the item of index i in menu is a ShowEditorAction, and
     * extracts the ShowEditorAction.
     *
     * @param menu The menu to get the JMenuItem, containing the action from.
     * @param i    The index where the JMenuItem is located in menu.
     * @return The ShowEditorAction if confirmed. Otherwise null.
     */
    private ShowEditorAction getShowEditorAction(JMenu menu, int i) {
        JMenuItem menuItem = recentEditorsMenu.getItem(i);
        if (menuItem == null) {
            return null;
        }

        Action unknownAction = menuItem.getAction();

        if (unknownAction == null || !(unknownAction instanceof ShowEditorAction)) {
            return null;
        }

        return (ShowEditorAction) unknownAction;
    }

    /**
     * Checks whether a JMenuItem is an Empty marker (marking an empty list).
     *
     * @param item The item to check.
     * @return True if item is an Empty marker. False otherwise (including if
     *         item is null).
     */
    private boolean isEmptyMarker(JMenuItem item) {
        if (item == null) {
            return false;
        }
        if (item.getText().equals(EMPTYMARKER)) {
            return true;
        }
        return false;
    }

	/*
     * Action classes for clearing all items in the Recent
	 * Editors/Projects/Workspaces submenu
	 */

    @SuppressWarnings("serial")
    private class ClearProjectsAction extends AbstractAction {
        public ClearProjectsAction() {
            super(CLEAR_ITEMS);
            putValue(Action.SHORT_DESCRIPTION, "Clear all recent projects");
        }

        public void actionPerformed(ActionEvent e) {
            if (!UISupport.confirm("Remove all Projects from this menu?", "Question")) {
                return;
            }

            StringToStringMap emptyMap = new StringToStringMap();

            SoapUI.getSettings().setString(RECENT_PROJECTS_SETTING, emptyMap.toXml());
            updateRecentProjectsMenu();

        }
    }

    @SuppressWarnings("serial")
    private class ClearWorkspacesAction extends AbstractAction {
        public ClearWorkspacesAction() {
            super(CLEAR_ITEMS);
            putValue(Action.SHORT_DESCRIPTION, "Clear all recent workspaces");
        }

        public void actionPerformed(ActionEvent e) {
            if (!UISupport.confirm("Remove all Workspaces from this menu?", "Question")) {
                return;
            }

            StringToStringMap emptyMap = new StringToStringMap();

            SoapUI.getSettings().setString(RECENT_WORKSPACES_SETTING, emptyMap.toXml());
            updateRecentWorkspacesMenu();

        }
    }

    @SuppressWarnings("serial")
    private class ClearEditorsAction extends AbstractAction {
        public ClearEditorsAction() {
            super(CLEAR_ITEMS);
            putValue(Action.SHORT_DESCRIPTION, "Clear all recent Editors");
        }

        public void actionPerformed(ActionEvent e) {
            if (!UISupport.confirm("Remove all Editors from this menu?", "Question")) {
                return;
            }

            recentEditorsMenu.removeAll();
            recentEditorsMenu.add(EMPTYMARKER).setEnabled(false);
            recentEditorsMenu.addSeparator();
            recentEditorsMenu.add(new ClearEditorsAction());
        }
    }

}
